package com.oromwin.repos.socket.request.file;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import org.apache.commons.logging.Log;

import com.oromwin.repos.common.Common;
import com.oromwin.repos.common.Logger;
import com.oromwin.repos.common.ReposConfig;
import com.oromwin.repos.common.ReposException;
import com.oromwin.repos.common.Common.Category;
import com.oromwin.repos.common.Common.ReposResult;
import com.oromwin.repos.common.Common.Status;
import com.oromwin.repos.common.helper.DBHelper;
import com.oromwin.repos.common.helper.FSHelper;
import com.oromwin.repos.common.helper.IOHelper;
import com.oromwin.repos.data.AbstractReposTable;
import com.oromwin.repos.data.FileTable;
import com.oromwin.repos.data.MediaTable;
import com.oromwin.repos.data.VolumeTable;
import com.oromwin.repos.server.LockObject;
import com.oromwin.repos.socket.request.AbstractRequest;
import com.oromwin.repos.socket.request.RequestAction;
import com.oromwin.repos.socket.response.Response;
import com.oromwin.repos.socket.response.file.CreateFileResponse;

/*
 * reflection
 * XXX: must flush, if not packet exists in local network buffer
 * XXX: must not use ObjectInputStream before output stream... freezing socket
 */
public class CreateFileRequest extends AbstractRequest implements RequestAction {
    private transient Log log = Logger.getLog(CreateFileRequest.class);
    private String VolumeName = null;
    private File FilePath = null;
    // other info.
    private String FileFormat = null;
    private long FileBytes = 0L;
    private File TempFilePath = null;
    
    public CreateFileRequest() {
    }
    
    public CreateFileRequest(String volumeName, File filePath) {
        VolumeName = volumeName;
        FilePath = filePath;
    }
    
    @Override
    public void receive(DataInputStream dis, LockObject lockObject) throws ReposException {
        try {
            synchronized (lockObject) {
                VolumeName = IOHelper.readUTF8(dis);
                FilePath = new File(IOHelper.readUTF8(dis));
                FileFormat = Common.getFileFormat(FilePath.getName());
                FileBytes = dis.readLong();
                _volumeTable = (VolumeTable) getParentReposTable();
                checkFreeSpaceExists(); // set _mediaTable
                checkDependentObjectNotExists();
                checkSameObjectExists();
                // receive file
                File mediaPath = _mediaTable.getMediaPath();
                FilePath = Common.FilePath.generate(mediaPath, id());
                TempFilePath = new File(FilePath + File.separator + id());
                int off = 0;
                int len = ReposConfig.getInstance().getReposSocketIOBuffSize();
                int read = 0;
                byte[] b = new byte[len];
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TempFilePath));
                while (read < FileBytes) {
                    int readBytes = dis.read(b, off, len);
                    read += readBytes;
                    bos.write(b, off, readBytes);
                }
                bos.flush();
                bos.close();
            }
        } catch (NullPointerException e) {
            String message = String.format("Can't find requested directory, path {%s}", TempFilePath);
            throw ReposException.create(log, e, message);
        } catch (SecurityException e) {
            String message = String.format("Can't access to requested directory, path {%s}", TempFilePath);
            throw ReposException.create(log, e, message);
        } catch (IOException e) {
            String message = String.format("Failed to read stream, {%s}", e.getMessage());
            throw ReposException.create(log, e, message);
        }
    }

    public Response run() {
        CreateFileResponse res = null;
        try {
            String volumeId = _mediaTable.getVolumeId();
            String mediaId = _mediaTable.getMediaId();
            String fileId = id();
            String fileCategory = Category.NORMAL;
            Status fileStatus = Common.Status.CREATE;
            Boolean pdfConvFlag = false;
            Boolean compressionFlag = false;
            FileTable fileTable = new FileTable(volumeId, mediaId, fileId, FileFormat, FilePath, FileBytes, fileCategory, fileStatus, pdfConvFlag, compressionFlag, Common.datetime(), Common.datetime());
            DBHelper.insertTable(fileTable);
            res = new CreateFileResponse(ReposResult.Success, "Succeeded to create file", fileId);
        } catch (ReposException e) {
            res = new CreateFileResponse(ReposResult.Failure, e.getMessage(), id());
        }
        return res;
    }

    @Override
    public void send(DataOutputStream dos) throws ReposException {
        try {
            IOHelper.writeUTF8(dos, this.getClass().getCanonicalName());
            IOHelper.writeUTF8(dos, VolumeName);
            IOHelper.writeUTF8(dos, FilePath.toString());
            long fileLength = FilePath.length();
            dos.writeLong(fileLength);
            int off = 0;
            int len = ReposConfig.getInstance().getReposSocketIOBuffSize();
            int read = 0;
            byte[] b = new byte[len];
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(FilePath));
            while ((read = bis.read(b, off, len)) > 0) {
                dos.write(b, off, read);
            }
            dos.flush();
        } catch (NullPointerException e) {
            String message = String.format("Can't find file, path {%s}", FilePath);
            throw ReposException.create(log, e, message);
        } catch (IOException e) {
            String message = String.format("Failed to write into output stream, {%s}", e.getMessage());
            throw ReposException.create(log, e, message);
        }
    }

    @Override
    public void checkFreeSpaceExists() throws ReposException {
        String whereClause = String.format("VolumeId = '%s' AND MediaStatus < 2", _volumeTable.getVolumeId());
        List<AbstractReposTable> mediaTables = DBHelper.selectTable(MediaTable.class, whereClause);
        long freespace = 0L;
        long maxFreespace = 0L;
        for (AbstractReposTable table : mediaTables) {
            MediaTable mediaTable = null;
            if (table instanceof MediaTable) {
                mediaTable = (MediaTable) table;
            } else {
                String message = String.format("Illegal media table type, class {%s}", table.getClass().toString());
                throw ReposException.create(log, message);
            }
            if (maxFreespace == 0L || maxFreespace < mediaTable.getMediaFreeBytes()) {
                _mediaTable = mediaTable;
                maxFreespace = mediaTable.getMediaFreeBytes();
            }
            freespace += mediaTable.getMediaFreeBytes();
        }
        long requested = FileBytes;
        if (freespace < requested) {
            String message = String.format("Requested file size is too big, freespace:requested {%d:%d}", freespace, requested);
            throw ReposException.create(log, message);
        }
    }

    @Override
    public void checkSameObjectExists() throws ReposException {
        File file = new File(FilePath, id());
        if (FSHelper.ls(file)) {
            String message = String.format("Same filename already exist on the server, path {%s}", file);
            throw ReposException.create(log, message);
        }
    }

    @Override
    public void checkDependentObjectNotExists() throws ReposException {
        String VolumeId = _mediaTable.getVolumeId();
        File MediaPath = _mediaTable.getMediaPath();
        String whereClause = String.format("VolumeId = '%s' AND MediaStatus < 2 AND MediaPath = '%s'", VolumeId, MediaPath);
        List<AbstractReposTable> list = DBHelper.selectTable(MediaTable.class, whereClause);
        if (list.size() == 0) {
            String message = String.format("VolumeName:VolumeId:MediaName {%s:%s:%s} is does not exists in database", VolumeName, VolumeId, MediaPath);
            throw ReposException.create(log, message); 
        }
    }

    @Override
    public AbstractReposTable getCurrentReposTable() throws ReposException {
        throw ReposException.create(log, Common.Message.NOT_IMPLEMENTED_METHOD_INVOKE);
    }

    @Override
    public AbstractReposTable getParentReposTable() throws ReposException {
        AbstractReposTable reposTable = null;
        String whereClause = String.format("VolumeStatus < 2 AND VolumeName = '%s'", VolumeName);
        List<AbstractReposTable> reposTables = DBHelper.selectTable(VolumeTable.class, whereClause);
        if (reposTables != null && reposTables.size() == 1) {
            reposTable = reposTables.get(0);
        } else {
            String message = String.format("'%s' VolumeName does not exists in database, tableSize:whereClause {%d:%s}", VolumeName, reposTables.size(), whereClause);
            throw ReposException.create(log, message); 
        }
        return reposTable;
    }
}
